#ifndef __tenacitas_command__sender__h__
#define __tenacitas_command__sender__h__


// =============================================================================
/** @file
    This file defines the sender class
*/


// =============================================================================
//                                 C++ Headers
#include <iostream>
#include <cstdint>
#include <string>
// =============================================================================
//                                 3rds Headers


// =============================================================================
//                                 Our Headers


// =============================================================================
//                                 Namespaces
using namespace std;


// =============================================================================
//                                 Macro Contants
/** */


// =============================================================================
//                                 Macro Commands
/** */


/** namespace of the organization */
namespace tenacitas {
    /** namespace for the project */
    namespace command {

        // =========================== Typedefs ===========================
        
        // ============================ Pre-Declarations ==================

        // ============================ Attributes ========================
        /** */

        // ============================ Functions =========================
        /** */

        // ============================ Classes ===========================

        
        /** Sends messages, translated from an application data, through some
            type of connection, and waits for a reply message.
            @nosubgrouping 
        */
        template <
            typename t_message_types,
            typename t_app_data_types,
            typename t_connector_types,
            typename t_connection_types,
            typename t_message_io_types,
            typename t_translator_types,
            typename t_timeout_types
            >
        class sender {
            
        public:

            // --- sender - Public Internal Types ------------------------

            typedef t_message_types message_types;
            typedef typename message_types::message message;
            typedef typename message_types::message_ptr message_ptr;

            typedef t_app_data_types app_data_types;
            typedef typename app_data_types::app_data app_data;
            typedef typename app_data_types::app_data_ptr app_data_ptr;

            typedef t_connection_types connection_types;
            typedef typename connection_types::connection connection;
            typedef typename connection_types::connection_ptr connection_ptr;

            typedef t_translator_types translator_types;
            typedef typename translator_types::translator_factory translator_factory;
            typedef typename translator_types::translator_factory_ptr
            translator_factory_ptr; 
            typedef typename translator_types::translator translator;
            typedef typename translator_types::translator_ptr translator_ptr;

            typedef t_connector_types connector_types;
            typedef typename connector_types::connector_factory connector_factory;
            typedef typename connector_types::connector_factory_ptr
            connector_factory_ptr; 
            typedef typename connector_types::connector connector;
            typedef typename connector_types::connector_ptr connector_ptr;

            typedef t_timeout_types timeout_types;
            typedef typename timeout_types::timeout_factory timeout_factory;
            typedef typename timeout_types::timeout_factory_ptr
            timeout_factory_ptr; 
            typedef typename timeout_types::timeout timeout;
            typedef typename timeout_types::timeout_ptr timeout_ptr;

            typedef t_message_io_types message_io_types;
            typedef typename message_io_types::message_io_factory message_io_factory;
            typedef typename message_io_types::message_io_factory_ptr
            message_io_factory_ptr; 
            typedef typename message_io_types::message_io message_io;
            typedef typename message_io_types::message_io_ptr message_io_ptr;


            // --- sender - Public Constructors --------------------------

            /** Constructor */
            sender( translator_factory_ptr p_translator_factory,
                    connector_factory_ptr p_connector_factory,
                    timeout_factory_ptr p_timeout_factory,
                    message_io_factory_ptr p_message_io_factory )
                : m_connection( ),
                  m_translator_factory( p_translator_factory ),
                  m_connector_factory( p_connector_factory ),
                  m_timeout_factory( p_timeout_factory ),
                  m_message_io_factory( p_message_io_factory ) {

                if ( !m_translator_factory ) {
                    throw std::string( "Empty translator" );
                }

                if ( !m_connector_factory ) {
                    throw std::string( "Empty connector" );
                }

                if ( !m_timeout_factory ) {
                    throw std::string( "Empty timeout" );
                }

                if ( !m_message_io_factory ) {
                    throw std::string( "Empty message IO" );
                }
                
                // object that will establish a connection to (possibly remote)
                // software 
                connector_ptr l_connector = m_connector_factory->create( );

                if ( !l_connector ) {
                    // @todo log critical; throw exception
                    std::cout << "Error creating connector" << std::endl;
                }

                // connection to (possibly remote) software 
                m_connection = l_connector->connect( );

                if ( !m_connection ) {
                    // @todo log critical and throw
                    std::cout << "Unable to connect" << std::endl;
                }
                else {
                    std::cout << "Connected" << std::endl;
                }                
            }
            
            /** Destructor */
            ~sender( ) {}

            // --- sender - Public Accessors -----------------------------

            // --- sender - Public Helpers -------------------------------

            // --- sender - Public Processors ----------------------------

            /** Sends an application data, and waits for a reply

                @param [in] p_app_data is the application data that is to be
                sent

                @return an application data sent as answer
            */
            app_data_ptr send( const app_data_ptr p_app_data ) {

                app_data_ptr l_rc;

                std::cout << "Original app data: '" << *p_app_data << "'" <<
                    std::endl; 

                // object that will translate @p p_app_data to a message object
                translator_ptr l_translator = m_translator_factory->create( );

                // translation of  @p p_app_data to a message object
                message_ptr l_message = l_translator->to_message( p_app_data );

                std::cout << "Translated app data: '" << *l_message << "'" <<
                    std::endl; 

                // object that will send the message through the connection
                message_io_ptr l_message_io = m_message_io_factory->create( );

                // sends the message
                if( !l_message_io->write( m_connection, l_message ) ) {
                    // @todo log error
                    std::cout << "Error sending message" << std::endl;
                    return l_rc;
                }
                else {
                    std::cout << "Message sent" << std::endl;
                }

                std::cout << "Waiting for reply" << std::endl;

                // waits for reply
                message_ptr l_reply = l_message_io->read( m_connection );

                std::cout << "Message received: '" << *l_reply << "'" <<
                    std::endl; 

                // translates the reply received into an application data
                l_rc = l_translator->to_app_data( l_reply );

                std::cout << "Reply translated to app data: '" << *l_rc << "'" <<
                    std::endl;

                return l_rc;
            }
            
                
            // --- sender - Public Operators -----------------------------

            // --- sender - Public Attributes ----------------------------

        private:

            // --- sender - Private Attributes ---------------------------

            connection_ptr m_connection;
            translator_factory_ptr m_translator_factory;
            connector_factory_ptr m_connector_factory;
            timeout_factory_ptr m_timeout_factory;
            message_io_factory_ptr m_message_io_factory;
        };
    }
}

// =============================================================================
//                     I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::command;


#endif
